package estrutura;


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

import controladoresBD.MateriasJpaController;
import controladoresBD.exceptions.NonexistentEntityException;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.persistence.*;
import javax.swing.DefaultListModel;
import javax.swing.ListModel;
import org.jgrapht.DirectedGraph;
import org.jgrapht.graph.*;

/**
 * Armazena uma lista das disciplinas que foram adicionadas e constrói um grafo
 * das disciplinas adicionadas.
 * 
 * @author fabio
 */
public class Grafo {
    /**
     * Grafo das disciplinas.
     */
    private DirectedGraph<Materias, DefaultEdge> grafo;
    /**
     * Lista das disciplinas adicionadas.
     */
    private List<String> lista;
    /**
     * Entity Manager Factory do banco de dados.
     */
    private EntityManagerFactory emf;
    /**
     * Controlador que acessa o banco de dados para buscar as matérias adicionadas.
     */
    private MateriasJpaController materiasController;
    
    /**
     * Construtor padrão.
     * Cria um objeto da classe com um EntityManager Factory que estabelece uma
     * conexão com o banco de dados através da unidade de persitência Modulo10PU.
     */
    public Grafo() {
        grafo = new DefaultDirectedGraph<>(DefaultEdge.class);
        lista = new LinkedList();
        emf = Persistence.createEntityManagerFactory("Modulo10PU");
        materiasController = new MateriasJpaController(emf);
    }
    
    /**
     * Construtor da classe com um Entity Manager Factory.
     * Cria um objeto da classe com uma unidade de persistência já definida.
     * @param emf EntityManagerFactory que já possui uma unidade de persistência
     * definida.
     */
    public Grafo(EntityManagerFactory emf) {
        grafo = new DefaultDirectedGraph<>(DefaultEdge.class);
        lista = new LinkedList();
        this.emf = emf;
        materiasController = new MateriasJpaController(emf);
    }
    
    /**
     * Adiciona uma disciplina ao grafo, caso ela não ainda não exista no grafo.
     * @param materia Sigla da disciplina a ser adicionada.
     * @return true - Caso a disciplina ainda não tenha sido adicionada e ela
     * exista no banco de dados.
     * @throws controladoresBD.exceptions.NonexistentEntityException
     */
    public boolean addDisciplina (String materia) throws NonexistentEntityException {
        Materias disciplina = materiasController.findMaterias(materia);
        
        if (disciplina == null) {
            throw new NonexistentEntityException("A disciplina " + materia + " não existe.");
        }
                
        if (grafo.addVertex(disciplina)) {
            lista.add(materia);
            return true;
        }
        else
            return false;
    }
    
    /**
     * Remove uma disciplina do grafo.
     * @param materia Sigla da disciplina a ser removida do grafo
     * @return true - Caso a disciplina já exista no grafo e foi removida com
     * sucesso.
     */
    public boolean delDisciplina (String materia) {
        Materias[] disciplinas = grafo.vertexSet().toArray(new Materias[grafo.vertexSet().size()]);
        
        for (Materias disciplina : disciplinas) {
            if (materia.equalsIgnoreCase(disciplina.getSigla())) {
                lista.remove(materia);
                return grafo.removeVertex(disciplina);
            }
        }
        
        return false;
    }
     
    /**
     * Adiciona uma lista de disciplinas ao grafo.
     * @param materias Lista de disciplinas a serem adicionadas ao grafo.
     */
    public void addLista(LinkedList<String> materias){
        Iterator<String> it = materias.iterator();
        
        while (it.hasNext()) {
            try {
                this.addDisciplina(it.next());
            } catch (NonexistentEntityException ex) {
                Logger.getLogger(Grafo.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
    
    /**
     * @return Lista com as matérias adicionadas.
     */
    public List<String> getLista() {
        return lista;
    }
    
    /**
     * @return Retorna uma lista das matérias adicionadas na interface ListModel.
     */
    public DefaultListModel getjList() {
        DefaultListModel model = new DefaultListModel();
        model.clear();
        String test;
        if(!(lista.isEmpty())){
            Iterator<String> iter1 = lista.iterator();
            while(iter1.hasNext()){
                test = iter1.next();
                model.addElement(test);
            }
        }
        return model;
    }
   
    /**
     * Remove completamente o grafo, removendo todos os vertices e suas arestas.
     */
    public void delGrafo() {
        Materias[] disciplinas = grafo.vertexSet().toArray(new Materias[grafo.vertexSet().size()]);
        
        for (Materias disciplina : disciplinas)
            grafo.removeVertex(disciplina);
        
        lista.clear();
    }
    
    /**
     * Retorna um grafo das matérias adicionadas.
     */
    DirectedGraph<Materias, DefaultEdge> getGrafo() {
        return grafo;
    }
    
    /**
     * Constroi as arestas do grafo a partir do conjunto de disciplinas adicionadas.
     */
    public void constroiArestas() {
        Set<Materias> conjuntoMaterias = grafo.vertexSet();
        
        for (Materias disciplina : conjuntoMaterias) {
            Collection<Grupo> colecaoPreReqs;
            colecaoPreReqs = disciplina.getPreReqs();
            Grupo[] preReqs;
            preReqs = (Grupo []) colecaoPreReqs.toArray(new Grupo[colecaoPreReqs.size()]);
            int index = selecionaGrupoPreReq(preReqs);
            if (index != -1) {
                for (Materias materia : conjuntoMaterias) {
                    if (!materia.equals(disciplina)) {
                        if (preReqs[index].contains(materia)) {
                            grafo.addEdge(materia, disciplina);
                        }
                    }
                }
            }
        }
    }

    
    /**
     * Seleciona um grupo de pré-requisitos
     * @param preReqs Vetor de grupos de pré-requisitos.
     * @return O índice do grupo escolhido. Caso não exista um grupo para escolher,
     * retorna -1.
     */
    private int selecionaGrupoPreReq(Grupo[] preReqs) {
        Set<Materias> conjuntoMaterias = grafo.vertexSet();
        
        for (int i = 0; i < preReqs.length; i++) {
            for (Materias disciplina : conjuntoMaterias) {
                if (preReqs[i].contains(disciplina)) {
                    return i;
                }
            }
        }
        return -1;
    }
}